package world

import (
	"MetroBuilderGameJam/gameEngine/drawer"
	"MetroBuilderGameJam/gameEngine/geom"
	"fmt"
	"image/color"
	"math/rand"
	"slices"
)

type CityStationBuilder interface {
	BuildNextStation(existsStations []*Station) *Station
	//GetFrequency() int
}

type PassengerFlowOrganizer interface {
	AddPassenger(existsStations []*Station) *Passenger
}

type RailRoadBranch interface {
	From() *geom.Point
	To() *geom.Point
	Inverse()
	IsInversed() bool
	FromStation() *Station
	ToStation() *Station
	RailRoad() *RailRoad
	CalcBendPosition() geom.Point
}

var _ RailRoadBranch = &BranchEdge{}
var _ RailRoadBranch = &BranchCompleted{}
var _ RailRoadBranch = &BranchProject{}
var _ RailRoadBranch = &BranchRemembered{}

func NewCity(builder CityStationBuilder, passengerFlow PassengerFlowOrganizer) *City {
	c := &City{
		trains: []*Train{
			NewTrain(),
			NewTrain(),
			NewTrain(),
		},
		trainsPlan:             []*TrainPlan{},
		stations:               []*Station{},
		builder:                builder,
		civils:                 passengerFlow,
		MaxPassengersOnStation: 15,
	}

	c.lines = []*RailRoad{
		NewRailRoad(c, drawer.ColorGold, true),
		NewRailRoad(c, drawer.ColorRed, true),
		NewRailRoad(c, drawer.ColorBlue, true),
		NewRailRoad(c, drawer.ColorCyan, false),
		NewRailRoad(c, drawer.ColorGreen, false),
		NewRailRoad(c, drawer.ColorMagenta, false),
		NewRailRoad(c, drawer.ColorPurple, false),
		NewRailRoad(c, drawer.ColorBrown, false),
	}

	c.stations = append(c.stations, builder.BuildNextStation(c.stations))
	c.stations = append(c.stations, builder.BuildNextStation(c.stations))
	c.stations = append(c.stations, builder.BuildNextStation(c.stations))

	return c
}

type City struct {
	lines      []*RailRoad
	trains     []*Train
	trainsPlan []*TrainPlan
	stations   []*Station

	builder CityStationBuilder
	civils  PassengerFlowOrganizer

	activeBranch             RailRoadBranch
	branchesState            []RailRoadBranch
	draggedTrain             *TrainPlan
	MaxPassengersOnStation   int
	gameOverStation          *Station
	deliveredPassengersCount int
}

func (c *City) GetLines() []*RailRoad {
	return c.lines
}

func (c *City) StopActiveItem() {
	branchProject := c.GetActiveProjectBranch()
	if branchProject != nil && branchProject.ChangedBranch != nil {
		bc := branchProject.ChangedBranch
		d1 := bc.CalcBendPosition().DistanceTo(*branchProject.To())
		temp := NewRememberedBranch(bc)
		temp.Inverse()
		d2 := temp.CalcBendPosition().DistanceTo(*branchProject.To())
		if d2 < d1 {
			bc.Inverse()
		}
	}
	c.activeBranch = nil
	if c.draggedTrain != nil {
		if c.draggedTrain.ToBranch != nil {
			c.trainsPlan = append(c.trainsPlan, c.draggedTrain)
			c.draggedTrain.IsPlaced = true
		} else {
			c.draggedTrain.Base.CancelRelocatePlanning()
		}
		c.draggedTrain = nil
	}
	for _, t := range c.GetStoredTrains() {
		if t.RelocationPlan != nil && !t.RelocationPlan.IsPlaced {
			t.CancelRelocatePlanning()
		}
	}
}

func (c *City) NewLineBuildPoint(p *geom.Point) {
	if p == nil {
		c.activeBranch = nil
		return
	}

	projectBranch := c.GetActiveProjectBranch()
	if projectBranch == nil {
		return
	}

	projectBranch.SetPoint(p)
}

func (c *City) GetBranches() []RailRoadBranch {
	return c.branchesState
}

func (c *City) GetStations() []*Station {
	return c.stations
}

func (c *City) GetTrainPlans() []*TrainPlan {
	tgs := make([]*TrainPlan, 0, len(c.trainsPlan)+1)
	tgs = append(tgs, c.trainsPlan...)
	tgs = append(tgs, c.draggedTrain)
	return tgs
}

func (c *City) GetTrains() []*Train {
	return c.trains
}

func (c *City) InverseBuildingBranch() {
	b := c.GetActiveProjectBranch()
	if b != nil {
		b.Inverse()
		if b.ChangedBranch != nil {
			b.ChangedBranch.Inverse()
		}
	}
}

func (c *City) Update(dt float64) {
	c.recalculateBranchesState()
	c.recalculateStationsState()
	c.updateTrainsState(dt)
}

func (c *City) TimeForNewStation() {
	c.stations = append(c.stations, c.builder.BuildNextStation(c.stations))
}

func (c *City) TimeForNewPassenger() {
	probability := rand.Intn(len(c.stations)*4 + 100)
	//fmt.Printf("Probability: %d\n", probability)
	if probability > 50 {
		c.civils.AddPassenger(c.stations)
	}
}

func (c *City) IsBuildingProcess() bool {
	return c.GetActiveProjectBranch() != nil
}

func (c *City) GetActiveProjectBranch() *BranchProject {
	if c.activeBranch == nil {
		return nil
	}
	if projectBranch, ok := c.activeBranch.(*BranchProject); ok {
		return projectBranch
	}
	return nil
}

func (c *City) GetFreeLine() *RailRoad {
	for _, line := range c.lines {
		if line.IsAvailable() && len(line.branches) == 0 {
			return line
		}
	}
	return nil
}

func (c *City) StartProjectBranchFromStation(station *Station) {
	if station == nil {
		return
	}

	projectBranch := c.GetActiveProjectBranch()
	if projectBranch != nil {
		return
	}

	freeLine := c.GetFreeLine()
	if freeLine == nil {
		return
	}
	projectBranch = NewBranchProject(station, freeLine)
	c.activeBranch = projectBranch
}

func (c *City) StartProjectBranchFromEdge(edge *BranchEdge) {
	if edge == nil {
		return
	}

	projectBranch := c.GetActiveProjectBranch()
	if projectBranch != nil {
		return
	}

	c.activeBranch = NewBranchProject(edge.FromStation(), edge.RailRoad())
}

func (c *City) ProjectToChangeCompletedBranch(branch *BranchCompleted) {
	if branch == nil {
		return
	}

	projectBranch := NewBranchProject(branch.FromStation(), branch.RailRoad())
	projectBranch.ChangedBranch = branch
	c.activeBranch = projectBranch
	//fmt.Println("change completed branch ", branch)
}

func (c *City) BuildRailRoadToggleStation(station *Station) {
	if station == nil {
		return
	}
	branchProject := c.GetActiveProjectBranch()
	if branchProject == nil {
		return
	}
	if branchProject.WrongBuilding != nil {
		c.StopActiveItem()
		fmt.Printf("Wrong building: %v\n", branchProject.WrongBuilding)
		return
	}

	defer c.forgotConnections()

	currentRoad := branchProject.RailRoad()
	if !currentRoad.HasStation(station) || branchProject.canLoopRoadIn(station) {
		if branchProject.ChangedBranch == nil {
			c.tryToCompleteBranchProject(station, branchProject)
		} else {
			c.changeCompletedBranch(station, branchProject)
		}
		c.forgotPathToRoadForTrains(currentRoad.GetColor())
		branchProject.SetFromStation(station)
		return
	}
	if currentRoad.Count() == 0 {
		c.forgotPathToRoadForTrains(currentRoad.GetColor())
		c.StopActiveItem()
		return
	}
	oldBranches := make([]BranchCompleted, 0, len(currentRoad.branches))
	for _, branch := range currentRoad.branches {
		oldBranches = append(oldBranches, *branch)
	}
	currentRoad.ExcludeExistsStationByProject(station, branchProject)
	if currentRoad.Count() == 0 {
		c.forgotPathToRoadForTrains(currentRoad.GetColor())
	} else {
		c.rememberPathToRoadForTrains(oldBranches, currentRoad.GetColor())
	}
}

func (c *City) changeCompletedBranch(station *Station, branchProject *BranchProject) {
	err := branchProject.road.AddStationToCompletedBranch(branchProject.ChangedBranch, station)
	if err == nil {
		branchProject.ChangedBranch = branchProject.road.GetBranchForStation(station)
		return
	}
	c.StopActiveItem()
	if wb, ok := err.(*BranchErrorAt); ok {
		branchProject.WrongBuilding = wb
	}
	fmt.Printf("Wrong building: %v\n", branchProject.WrongBuilding)
}

func (c *City) tryToCompleteBranchProject(station *Station, branchProject *BranchProject) {
	branch := CompleteBranchProject(branchProject, station)
	if branch == nil {
		c.StopActiveItem()
		fmt.Printf("Wrong building: %v\n", branchProject.WrongBuilding)
		return
	}
	isNewRoad := branchProject.RailRoad().Count() == 0
	err := branchProject.RailRoad().AddBranch(branch)
	if err != nil {
		c.StopActiveItem()
		return
	}
	if isNewRoad {
		trainOnRememberedRoad := false
		for _, t := range c.trains {
			if t.CurrentBranch != nil && t.CurrentBranch.RailRoad().GetColor() == branchProject.RailRoad().GetColor() {
				trainOnRememberedRoad = true
			}
		}
		if !trainOnRememberedRoad {
			t := c.GetNextFreeTrain()
			if t != nil {
				t.PlaceOnBranch(branch, branch.FromStation(), branch.From())
			}
		}
	}
}

type ItemWithRememberedBranch interface {
	IsRememberedBranchSameReal() bool
	GetRememberedBranch() *BranchRemembered
	IsOnBranch() bool
	IsOnStation() bool
}

func (c *City) recalculateBranchesState() {
	branches := make([]RailRoadBranch, 0, (c.lines[0].Count()+2)*len(c.lines))

	branchProject := c.GetActiveProjectBranch()
	if branchProject != nil {
		branches = append(branches, branchProject)
		if branchProject.ChangedBranch != nil {
			from := branchProject.ChangedBranch.to
			if from == branchProject.from {
				from = branchProject.ChangedBranch.from
			}
			bp := NewBranchProject(from, branchProject.road)
			bp.SetPoint(branchProject.to)
			branches = append(branches, bp)
		}
	}

	for _, line := range c.lines {
		if line.Count() > 0 {
			fe := line.GetFirstEdge()
			if fe != nil && (branchProject == nil || branchProject.from != fe.station || fe.RailRoad() != branchProject.road) {
				branches = append(branches, fe)
			}
			le := line.GetLastEdge()
			if le != nil && (branchProject == nil || branchProject.from != le.station || le.RailRoad() != branchProject.road) {
				branches = append(branches, le)
			}
		}
		for _, b := range line.branches {
			if branchProject == nil || !branchProject.IsChangeFor(b) {
				branches = append(branches, b)
			}
		}
	}

	for _, train := range c.GetTrainsAndPlansOnBranch() {
		rb := train.GetRememberedBranch()
		if !train.IsRememberedBranchSameReal() ||
			branchProject != nil && rb.HasStation(branchProject.FromStation()) {
			branches = append(branches, rb)
			for _, rba := range rb.Additional {
				branches = append(branches, rba)
			}
		}
	}

	c.branchesState = branches
}

func (c *City) recalculateStationsState() {
	for _, s := range c.stations {
		if len(s.Passengers()) >= c.MaxPassengersOnStation {
			c.GameOver(s)
		}
	}

	branchProject := c.GetActiveProjectBranch()
	if branchProject == nil {
		return
	}
	station := branchProject.FromStation()
	if station != nil {
		station.UpdateBranchesSlots(c.branchesState)
	}
	if branchProject.ChangedBranch != nil {
		if station == branchProject.ChangedBranch.FromStation() {
			station = branchProject.ChangedBranch.ToStation()
		} else {
			station = branchProject.ChangedBranch.FromStation()
		}
		station.UpdateBranchesSlots(c.branchesState)
	}
}

func (c *City) GetNextFreeTrain() *Train {
	for _, train := range c.trains {
		if train.CurrentBranch == nil {
			return train
		}
	}
	return nil
}

func (c *City) GetStoredTrains() []*Train {
	trains := make([]*Train, 0, len(c.trains))
	for _, train := range c.trains {
		if train.CurrentBranch != nil || c.draggedTrain != nil && train == c.draggedTrain.Base {
			continue
		}
		trains = append(trains, train)
	}
	return trains
}

func (c *City) DragTrainPlan(position geom.Point) {
	if c.draggedTrain != nil {
		c.draggedTrain.Position = &position
	}
}

func (c *City) StartDragTrain(train *Train, position geom.Point) {
	if train == nil || train.RelocationPlan != nil {
		return
	}

	c.draggedTrain = NewTrainPlan(train)
	train.SetRelocatePlanning(c.draggedTrain)
	c.draggedTrain.Position = &position
}

func (c *City) IsDragging() bool {
	return c.draggedTrain != nil
}

func (c *City) TryAcceptTrainPlanToBranch(branch *BranchCompleted) {
	if c.draggedTrain == nil {
		return
	}

	c.draggedTrain.ApplyToBranch(branch)
}

func (c *City) updateTrainsState(dt float64) {
	for _, train := range c.trains {
		train.Update(dt)
		c.deliveredPassengersCount += train.ReportAboutPassengerDeliveredCount()
	}

	for i := 0; i < len(c.trainsPlan); i++ {
		if c.trainsPlan[i].NeedToRemove || c.trainsPlan[i].IsPlaced && c.trainsPlan[i].ToBranch.RailRoad().Count() == 0 {
			if c.trainsPlan[i].Base != nil && c.trainsPlan[i].Base.RelocationPlan != nil {
				c.trainsPlan[i].Base.RelocationPlan = nil
			}
			c.trainsPlan = append(c.trainsPlan[:i], c.trainsPlan[i+1:]...)
			i--
		}
	}
}

func (c *City) GetTrainsAndPlansOnBranch() []ItemWithRememberedBranch {
	trainsOnBranch := make([]ItemWithRememberedBranch, 0, len(c.trains)+len(c.trainsPlan))
	for _, train := range c.trains {
		if train.IsOnBranch() && !train.IsOnStation() {
			trainsOnBranch = append(trainsOnBranch, train)
		}
	}
	for _, train := range c.trainsPlan {
		if train.IsOnBranch() {
			trainsOnBranch = append(trainsOnBranch, train)
		}
	}
	return trainsOnBranch
}

func (c *City) forgotPathToRoadForTrains(lineColor color.Color) {
	for _, train := range c.GetTrainsAndPlansOnBranch() {
		rb := train.GetRememberedBranch()
		if rb.RailRoad().GetColor() != lineColor {
			continue
		}
		if rb.RailRoad().Count() == 0 || rb.HasConnectionToRoad() {
			rb.Additional = rb.Additional[:0]
		} else {
			for i, branch := range rb.Additional {
				if !branch.HasConnectionToRoad() {
					rb.Additional = rb.Additional[:i]
					return
				}
			}
		}
	}
}

func (c *City) rememberPathToRoadForTrains(oldBranches []BranchCompleted, lineColor color.Color) {
	for _, train := range c.GetTrainsAndPlansOnBranch() {
		rb := train.GetRememberedBranch()
		if rb.RailRoad().GetColor() != lineColor {
			continue
		}
		if rb.HasPathToRoad() {
			continue
		}
		for _, branch := range oldBranches {
			if !rb.RailRoad().HasBranch(&branch) && !rb.HasBranch(&branch) {
				br := rb.AddBranch(&branch)
				if br.HasPathToRoad() {
					break
				}
			}
		}
	}
}

func (c *City) findAllConnectionsDeep(rr *RailRoad, excludeRoads []*RailRoad) []*RailRoadConnection {
	if rr == nil {
		return []*RailRoadConnection{}
	}
	lines := make([]*RailRoad, 0, len(c.lines))
	for _, r := range c.lines {
		if r == rr || slices.Contains(excludeRoads, r) {
			continue
		}
		lines = append(lines, r)
	}
	connections := make([]*RailRoadConnection, 0, len(lines))

	for _, s := range rr.Stations() {
		for _, r := range lines {
			if r.HasStation(s) {
				connections = append(connections, NewRailRoadConnection(s, rr, r))
			}
		}
	}

	connectedLines := make(map[*RailRoad]struct{}, len(lines))
	for _, con := range connections {
		for _, r := range con.Roads {
			connectedLines[r] = struct{}{}
		}
	}
	for r := range connectedLines {
		if r == rr || slices.Contains(excludeRoads, r) {
			continue
		}
		connections = append(connections, c.findAllConnectionsDeep(r, append(excludeRoads, rr))...)
	}
	return connections
}

func (c *City) forgotConnections() {
	for _, rr := range c.lines {
		rr.forgotConnections()
	}
}

func (c *City) GameOver(s *Station) {
	c.gameOverStation = s
}

func (c *City) GetGameOverStation() *Station {
	return c.gameOverStation
}

func (c *City) GetScore() int {
	return c.deliveredPassengersCount
}

func (c *City) TimeForNewLine() bool {
	c.trains = append(c.trains, NewTrain())
	for _, line := range c.lines {
		if !line.IsAvailable() {
			line.isAvailable = true
			return true
		}
	}
	return false
}
